﻿//==============================================================================  
//Copyright (C) 2012-2015 9UN.ORG. All rights reserved. 
//GUID：46172595-6b69-4526-b9da-426d663f5b05
//CLR Version: 4.0.30319.18033
//Code Author：Kevin Wang
//Contact：Email(Admin@9un.org),QQ(265382 or 74344)
//Filename：DoubleWindowBase
//Namespace：RuanRang.Media.Core
//Functions：DoubleWindowBase  
//Created by Kevin Wang at 2013/2/2 23:05:09 http://blog.9un.org
//============================================================================== 

#region Usings

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using RuanRang.Media.Core;
using RuanRang.Media.Windows.Interop;

#endregion

namespace RuanRang.Media.Windows
{
    /// <summary>
    /// Win32 implementation of <see cref="DoubleWindowBase" />.
    /// </summary>
    public class WindowsOSDoubleWindow : DoubleWindowBase
    {
        private const string ChildWindowClassName = "libvlc.net child vlc window";
        private const string MainWindowClassName = "libvlc.net main vlc window";
        private static IntPtr _moduleHandle;

        private readonly NativeMethods.Window _childWindowFirst;
        private readonly NativeMethods.Window _childWindowSecond;
        private readonly NativeMethods.Window _mainWindow;

        private uint _currentBackgroundColor;
        private int _currentHeight;
        private int _currentLeft;
        private int _currentTop;
        private int _currentWidth;
        private NativeMethods.WndProcDelegate _dispWndProc;
        private NativeMethods.WndProcDelegate _dispWndProc2;

        private bool _firstChildIsActive = true;
        private bool _isDisposed;

        private IntPtr _mainWindowSolidBrush;
        private bool _playerVisible;
        private bool _visible;
        private bool _windowClassRegistered;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public WindowsOSDoubleWindow()
        {
            _currentLeft = 0;
            _currentTop = 0;
            _currentWidth = 320 * 2;
            _currentHeight = 240;
            _visible = true;
            //
            RegisterWindowsClasses();
            //
            _mainWindow = new NativeMethods.Window(
                NativeMethods.WindowsStyle.WS_POPUP | NativeMethods.WindowsStyle.WS_VISIBLE,
                NativeMethods.WindowsStyleEx.WS_EX_TOPMOST | NativeMethods.WindowsStyleEx.WS_EX_NONE,
                MainWindowClassName, "vlc main window",
                _currentLeft, _currentTop, _currentWidth, _currentHeight);

            _childWindowFirst = new NativeMethods.Window(_mainWindow.Handle,
                                                        NativeMethods.WindowsStyle.WS_CHILD |
                                                        NativeMethods.WindowsStyle.WS_CLIPSIBLINGS
                                                        ,
                                                        NativeMethods.WindowsStyleEx.WS_EX_TOPMOST,
                                                        ChildWindowClassName, "vlc child window 1",
                //	0, 0, 320, 240);
            _currentLeft, _currentTop, _currentWidth, _currentHeight);

            _childWindowSecond = new NativeMethods.Window(_mainWindow.Handle,
                                                         NativeMethods.WindowsStyle.WS_CHILD |
                                                         NativeMethods.WindowsStyle.WS_CLIPSIBLINGS
                                                         ,
                                                         NativeMethods.WindowsStyleEx.WS_EX_TOPMOST,
                                                         ChildWindowClassName, "vlc child window 2",
                //	320, 0, 320, 240);
                                                         _currentLeft, _currentTop, _currentWidth, _currentHeight);
            //
            SetWindowVisibility(_mainWindow.Handle, _visible);
            SetWindowVisibility(_childWindowFirst.Handle, _visible);
            SetWindowVisibility(_childWindowSecond.Handle, _visible);
            Reorder(_childWindowFirst.Handle, _childWindowSecond.Handle);
        }

        /// <summary>
        /// Window width.
        /// </summary>
        public override int Width
        {
            get
            {
                return _currentWidth;
            }
            set
            {
                _currentWidth = value;
                ApplyWindowPosition();
            }
        }

        /// <summary>
        /// Window height.
        /// </summary>
        public override int Height
        {
            get
            {
                return _currentHeight;
            }
            set
            {
                _currentHeight = value;
                ApplyWindowPosition();
            }
        }

        /// <summary>
        /// Window left position.
        /// </summary>
        public override int Left
        {
            get
            {
                return _currentLeft;
            }
            set
            {
                _currentLeft = value;
                ApplyWindowPosition();
            }
        }

        /// <summary>
        /// Window top position.
        /// </summary>
        public override int Top
        {
            get
            {
                return _currentTop;
            }
            set
            {
                _currentTop = value;
                ApplyWindowPosition();
            }
        }

        /// <summary>
        /// Background color.
        /// </summary>
        public override uint BackgroundColor
        {
            get
            {
                return _currentBackgroundColor;
            }
            set
            {
                _currentBackgroundColor = value;
                //
                NativeMethods.DeleteObject(_mainWindowSolidBrush);
                _mainWindowSolidBrush = NativeMethods.CreateSolidBrush(_currentBackgroundColor);
                //
                NativeMethods.SetClassLong(_mainWindow.Handle,
                                           NativeMethods.WindowClassLongIndex.GCL_HBRBACKGROUND,
                                           _mainWindowSolidBrush.ToInt32());
                NativeMethods.InvalidateRect(_mainWindow.Handle, IntPtr.Zero, true);
            }
        }

        /// <summary>
        /// Background image file path.
        /// </summary>
        public override string BackgroundImageFilePath
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Is window visible.
        /// </summary>
        public override bool Visible
        {
            get
            {
                return _visible;
            }
            set
            {
                if (_visible != value)
                {
                    _visible = value;
                    //
                    SetWindowVisibility(_mainWindow.Handle, _visible);
                }
            }
        }

        /// <summary>
        /// Is active subwindow visible.
        /// </summary>
        protected override bool PlayerVisible
        {
            get
            {
                return (_playerVisible);
            }
            set
            {
                if (_playerVisible != value)
                {
                    _playerVisible = value;
                    //
                    if (_playerVisible)
                    {
                        SetWindowVisibility(GetInactiveWindowHandle(), true);
                        SetWindowVisibility(GetActiveWindowHandle(), true);
                    }
                    else
                    {
                        SetWindowVisibility(GetInactiveWindowHandle(), false);
                        SetWindowVisibility(GetActiveWindowHandle(), false);
                    }
                }
            }
        }

        private static IntPtr GetInstanceHandle()
        {
            if (_moduleHandle == IntPtr.Zero)
            {
                Module[] modules = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetModules(false);
                _moduleHandle = Marshal.GetHINSTANCE(modules[0]);
            }
            return _moduleHandle;
        }

        /// <summary>
        /// Handle of main window.
        /// </summary>
        /// <returns></returns>
        public IntPtr GetMainWindowHandle()
        {
            return (_mainWindow.Handle);
        }

        /// <summary>
        /// Clean up resources.
        /// </summary>
        /// <param name="isDisposing"></param>
        protected override void Dispose(bool isDisposing)
        {
            if (!_isDisposed)
            {
                _isDisposed = true;
                //
                if (_mainWindow != null)
                {
                    _mainWindow.ReleaseHandle();
                }
                if (_childWindowFirst != null)
                {
                    _childWindowFirst.ReleaseHandle();
                }
                if (_childWindowSecond != null)
                {
                    _childWindowSecond.ReleaseHandle();
                }
            }
        }

        /// <summary>
        /// Finalizer.
        /// </summary>
        ~WindowsOSDoubleWindow()
        {
            Dispose(false);
        }

        /// <summary>
        /// Registers the windows classes.
        /// </summary>
        private void RegisterWindowsClasses()
        {
            // do not register classes more than once
            if (_windowClassRegistered) return;
            _windowClassRegistered = true;
            //
            _mainWindowSolidBrush = NativeMethods.CreateSolidBrush(_currentBackgroundColor);
            //
            var wndClass = new NativeMethods.WNDCLASSEX_D
                               {
                                   cbSize = Marshal.SizeOf(typeof (NativeMethods.WNDCLASSEX_D)),
                                   hbrBackground = _mainWindowSolidBrush,
                                   hCursor = IntPtr.Zero,
                                   hIcon = IntPtr.Zero,
                                   hIconSm = IntPtr.Zero,
                                   hInstance = GetInstanceHandle(),
                                   lpszMenuName = null,
                                   style = 0x08,
                                   cbClsExtra = 0,
                                   cbWndExtra = 0
                               };
            // store delegate to not lose it for GC
            _dispWndProc = DispatchingEmptyWindowProcedure;
            wndClass.lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_dispWndProc);
            //
            wndClass.lpszClassName = MainWindowClassName;

            NativeMethods.RegisterClassEx(wndClass);
            //
            wndClass = new NativeMethods.WNDCLASSEX_D
                           {
                               cbSize = Marshal.SizeOf(typeof (NativeMethods.WNDCLASSEX_D)),
                               hbrBackground =
                                   NativeMethods.GetStockObject(
                                       NativeMethods.StockObjects.BLACK_BRUSH),
                               hCursor = IntPtr.Zero,
                               hIcon = IntPtr.Zero,
                               hIconSm = IntPtr.Zero,
                               hInstance = GetInstanceHandle(),
                               lpszMenuName = null,
                               style = 0x08,
                               cbClsExtra = 0,
                               cbWndExtra = 0
                           };
            // store delegate to not lose it for GC
            _dispWndProc2 = DispatchingEmptyWindowProcedure;
            wndClass.lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_dispWndProc2);
            //
            wndClass.lpszClassName = ChildWindowClassName;
            NativeMethods.RegisterClassEx(wndClass);
        }

        //[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        /// <summary>
        /// Dispatchings the empty window procedure.
        /// </summary>
        /// <param name="wnd">The WND.</param>
        /// <param name="msg">The MSG.</param>
        /// <param name="param">The param.</param>
        /// <param name="lParam">The l param.</param>
        /// <returns>IntPtr.</returns>
        private static IntPtr DispatchingEmptyWindowProcedure(IntPtr wnd, uint msg, IntPtr param, IntPtr lParam)
        {
            if (msg == (uint)NativeMethods.WindowsMessages.WM_NCCREATE)
            {
                // return 1 to create window, another value means error when creating
                return (new IntPtr(1));
            }
            if (msg == (uint)NativeMethods.WindowsMessages.WM_CLOSE)
            {
                // destroy window after closing
                NativeMethods.DestroyWindow(wnd);
            }
            // dispatch to default procedure
            return (NativeMethods.DefWindowProc(wnd, (int)msg, param, lParam));
        }

        /// <summary>
        /// Applies the window position.
        /// </summary>
        private void ApplyWindowPosition()
        {
            NativeMethods.SetWindowPos(_mainWindow.Handle, 0, _currentLeft, _currentTop, _currentWidth,
                                       _currentHeight, 0);
            Reorder(GetActiveWindowHandle(), GetInactiveWindowHandle());
        }

        /// <summary>
        /// Reorders the specified handle first.
        /// </summary>
        /// <param name="handleFirst">The handle first.</param>
        /// <param name="handleSecond">The handle second.</param>
        private void Reorder(IntPtr handleFirst, IntPtr handleSecond)
        {
            NativeMethods.SetWindowPos(handleSecond, handleFirst, 0, 0, _currentWidth,
                                       _currentHeight, 0);
        }

        /// <summary>
        /// Sets the window visibility.
        /// </summary>
        /// <param name="hwnd">The HWND.</param>
        /// <param name="visibility">if set to <c>true</c> [visibility].</param>
        private static void SetWindowVisibility(IntPtr hwnd, bool visibility)
        {
            NativeMethods.ShowWindow(hwnd, visibility ? NativeMethods.CmdShow.SW_SHOW : NativeMethods.CmdShow.SW_HIDE);
        }

        /// <summary>
        /// Gets handle of active subwindow.
        /// </summary>
        /// <returns>OS specific handle of window.</returns>
        protected override IntPtr GetActiveWindowHandle()
        {
            return _firstChildIsActive ? _childWindowFirst.Handle : _childWindowSecond.Handle;
        }

        /// <summary>
        /// Gets handle of inactive subwindow.
        /// </summary>
        /// <returns>OS specific handle of window.</returns>
        protected override IntPtr GetInactiveWindowHandle()
        {
            return _firstChildIsActive ? _childWindowSecond.Handle : _childWindowFirst.Handle;
        }

        public IntPtr ActiveWindowHandle
        {
            get { return GetActiveWindowHandle(); }
        }

        /// <summary>
        /// Switches active and inactive subwindows.
        /// </summary>
        protected override void SwitchWindows()
        {
            //
            //setWindowVisibility(GetInactiveWindowHandle(), true);
            //setWindowVisibility(GetActiveWindowHandle(), true);
            //return;
            //
            _firstChildIsActive = !_firstChildIsActive;
            Reorder(GetActiveWindowHandle(), GetInactiveWindowHandle());
            if (_playerVisible)
            {
                SetWindowVisibility(GetInactiveWindowHandle(), true);
                SetWindowVisibility(GetActiveWindowHandle(), true);
            }
            else
            {
                SetWindowVisibility(GetInactiveWindowHandle(), false);
                SetWindowVisibility(GetActiveWindowHandle(), false);
            }
        }

        /// <summary>
        /// Sends the WM close.
        /// </summary>
        internal void SendWMClose()
        {
            NativeMethods.SendMessage(_mainWindow.Handle, (int)NativeMethods.WindowsMessages.WM_CLOSE, IntPtr.Zero,
                                      IntPtr.Zero);
        }
    }
}